/**
 *@author qzg
 *@description  分布式锁中间件
 *@createDate 2022年07月05日 19:12:31
 */
import { Middleware } from 'koa'
import { Lock } from 'redlock'
import redlock from '../db/redlock'
import { error } from '../utils/Response'
//这里isByUser为true则由用户id+请求地址作为key上锁，即：此接口不允许一个用户同时更改同一资源（参数不同也不行）
//isByUser默认为false则由全部参数+用户id+地址作为key上锁,即：此接口不允许一个用户同时以同一参数更改同一资源（拦截重复请求）
const idempotent = (isByUser: boolean = false) => {
  const Redlock: Middleware = async (ctx, next) => {
    let id: string
    //这里的ctx.user是我之前配置的中间件，用于解析用户携带token的参数，来辨别用户和获取用户参数，里面存放用户的个人信息
    //有的接口不需要鉴权认证,所以ctx?.user?.id则id以undefined输出
    /*这里为什么要解析出id而不是直接拿token呢？因为一个用户可以有多个token，但一个用户只有一个id
    如果拿token作为标识，不同token的同一用户也会成功上锁，就形成了一个用户多次获得了锁的情况
    但由于id的独立性，所以id不同，就表示为不同的用户了
    */
    id = ctx?.user?.id
    let lock: Lock
    try {
      if (isByUser) {
        //上锁
        lock = await redlock.acquire([`${id}:${ctx.URL}`], 10000)
      } else {
        const body = JSON.stringify(ctx.request.body)

        lock = await redlock.acquire([`${id}:${ctx.URL}:${body}`], 10000)
      }
    } catch (err) {
      //如果抛出错误表示上锁失败，表示有重复请求正在操作
      //这里的error()函数是我封装的返回错误的函数里面调用了ctx.throw所以报错会立即返回，后面的next不会继续进行
      error(ctx, 500, '请求正在进行,请勿重复提交')
    }
    //这里给next再套一次try finally是因为如果next里面有错误并抛出不做处理的话
    //会直接跳过lock.release()导致根本没有解锁，加一个finally让他不管有没有错误最后都能解锁
    //至于为什么不加catch因为此处next抛出的错误不应该由分布式锁中间件管，而是另外有一个错误处理中间件管理

    try {
      //后面的中间件全部执行完就可以释放锁了
      await next()
    } finally {
      await lock!.release()
    }

    //后面的中间件全部执行完就可以释放锁了
  }
  return Redlock
}
export default idempotent
